package org.uca.dss.curso1011.grupo9;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.joda.time.LocalDate;
import org.joda.time.LocalTime;
import org.uca.dss.curso1011.grupo9.interfazExtendido.InformacionTrayecto;
import org.uca.dss.curso1011.grupo9.interfazExtendido.Itinerario;

/**
 * Gestor de viajes
 * @author eldruin
 */
public class GestorViajes {
    private Map<Integer,Viaje> viajes;
    private int siguienteCodigoViaje;

    /**
     * Constructor
     */
    GestorViajes() 
    {
        viajes = new HashMap<Integer, Viaje> ();
        siguienteCodigoViaje = 0;
    }

    public static void init() throws Exception {
    }

    /**
     * Crea un viaje por cada trayecto. Cada viaje tiene sólo 1 trayecto, sin trasbordos
     * @param trayectos lista de trayectos
     */
    public void generarViajes(List<Trayecto> trayectos)
    {
        for(Trayecto t : trayectos)
        {
            List<Trayecto> lista = new ArrayList<Trayecto>();
            lista.add(t);
            crearViaje(getSiguienteIdViaje(), null, null, lista);
        }
    }

    /**
     * Si no existe un viaje con ese código, crea el viaje y lo guarda en viajes
     * @param codigoViaje
     * @param fecha_inicio
     * @param fecha_fin
     * @param trayectos
     */
    private void crearViaje(int codigoViaje, LocalDate fecha_inicio, LocalDate fecha_fin,
                          List<Trayecto> trayectos)
    {
        if (!existeViaje(codigoViaje))
        {
            Viaje viaje = new Viaje(codigoViaje, fecha_inicio, fecha_fin,
                                trayectos);
            viajes.put(codigoViaje, viaje);
        }
    }

    /**
     * Comprueba si existe un viaje con ese código en el sistema
     * @param codigoViaje
     * @return verdadero o falso dependiendo de si existe
     */
    public boolean existeViaje(int codigoViaje) {

        return (viajes.containsKey(codigoViaje));
    }

    /**
     * Devuelve el viaje con el código dado
     * @param codigoViaje
     * @return el viaje con el código pedido
     * @throws Excepciones si no se encuentra el viaje
     */
    public Viaje getViaje(int codigoViaje) throws Excepciones
    {
        
        if(viajes.get(codigoViaje)!=null){
            return viajes.get(codigoViaje);
        }
        else
        {
            // Lanzamos una excepcion para indicar que no existe el viaje
            throw new Excepciones("No existe el Viaje con código " + codigoViaje);
        }
    }

    /**
     * @return número de viajes almacenados
     */
    public int numeroViajes()
    {
        return viajes.size();
    }

    /**
     * Añadir trayecto a un viaje
     * @param codigoViaje código del viaje al que se va a añadir
     * @param trayecto trayecto a añadir
     * @throws Excepciones
     */
    public void addTrayecto(int codigoViaje, Trayecto trayecto) throws Excepciones
    {
        Viaje v = viajes.get(codigoViaje);
        if (v != null)
        {
            v.addTrayecto(trayecto);
            viajes.put(codigoViaje, v);
        }
        else{
            throw new Excepciones("No existe el Viaje con código " + codigoViaje);
        }

    }

    /**
     * Buscar los viajes que coinciden con los criterios de búsqueda
     * La fecha no se tiene en cuenta porque los viajes se producen todos los días
     * @param ciudadOrigen
     * @param ciudadDestino
     * @param fecha
     * @return lista de viajes encontrados
     */
    public List<Viaje> buscarViaje(String ciudadOrigen, String ciudadDestino,
                                   LocalDate fecha)
    {
        List<Viaje> viajes_resultado = new ArrayList<Viaje>();
        for (Map.Entry<Integer, Viaje> entry : viajes.entrySet())
        {
            Viaje v = entry.getValue();
            if (v.ciudadOrigen().equals(ciudadOrigen) &&
                v.ciudadDestino().equals(ciudadDestino) /*&&
                v.getFechaInicio().equals(fecha)*/)
            {
                v.setFechaInicio(fecha);
                viajes_resultado.add(v);
            }
        }

        List<List<Viaje>> viajes_trasbordos = buscarTrasbordos(ciudadOrigen, ciudadDestino, fecha, null, null);

        for (List<Viaje> viajes_trasbordo : viajes_trasbordos)
            if (viajes_trasbordo.size() >= 2)
            {
                List<Trayecto> trayectos = new ArrayList<Trayecto>();
                for (Viaje v : viajes_trasbordo)
                    trayectos.addAll(v.getTrayectos());

                // Viaje "virtual", sólo para devolver, no lo conoce el GestorViajes
                Viaje v = new Viaje(-1, fecha, fecha, trayectos);
                viajes_resultado.add(v);
            }

        return viajes_resultado;
    }

    /**
     * Busca un viaje que cumpla con los criterios especificados
     * @param ciudadOrigen
     * @param ciudadDestino
     * @param fecha
     * @param hora
     * @return primer viaje encontrado que cumpla los criterios
     */
    public Viaje buscarViaje(String ciudadOrigen, String ciudadDestino,
                             LocalDate fecha, LocalTime hora)
    {
        Viaje viaje = null;
        for (Map.Entry<Integer, Viaje> entry : viajes.entrySet())
        {
            Viaje v = entry.getValue();
            if (v.ciudadOrigen().equals(ciudadOrigen) &&
                v.ciudadDestino().equals(ciudadDestino) &&
                //v.getFechaInicio().equals(fecha) &&
                (hora == null || v.horaSalida().equals(hora)))
                viaje = v;
        }
        if (viaje != null)
            viaje.setFechaInicio(fecha);

        if (viaje == null)
        {
            List<List<Viaje>> viajes_trasbordos = buscarTrasbordos(ciudadOrigen, ciudadDestino, fecha, hora, null);
            if (viajes_trasbordos.size() > 0)
            {
                if (viajes_trasbordos.get(0).size() >= 2)
                {
                    List<Trayecto> trayectos = new ArrayList<Trayecto>();
                    for (Viaje v : viajes_trasbordos.get(0))
                        trayectos.addAll(v.getTrayectos());

                    // Viaje "virtual", sólo para devolver, no lo conoce el GestorViajes
                    viaje = new Viaje(-1, fecha, fecha, trayectos);
                }
            }
        }
        
        return viaje;
    }

    /**
     * Horarios de viajes con los criterios especificados
     * @param origen
     * @param destino
     * @param fecha
     * @return lista de horas de salida
     */
    public List<LocalTime> getHorarios(String origen, String destino, LocalDate fecha)
    {
        List<LocalTime> resultado = new ArrayList<LocalTime>();
        List<Viaje> viajes_resultado = new ArrayList<Viaje> ();
        viajes_resultado = buscarViaje(origen, destino, fecha);
        for (Viaje v : viajes_resultado)
            resultado.add(v.horaSalida());

        return resultado;
    }

    /**
     * Itinerarios disponibles con los criterios especificados
     * @param origen
     * @param destino
     * @param fechaSalida
     * @param horaSalida
     * @param horaLlegada
     * @return lista de itinerarios
     */
    public List<Itinerario> getItinerariosEntre(String origen, String destino, LocalDate fechaSalida, LocalTime horaSalida, LocalTime horaLlegada)
    {
        List<Itinerario> resultado = new ArrayList();

        Viaje viaje = null;
        for (Map.Entry<Integer, Viaje> entry : viajes.entrySet())
        {
            Viaje v = entry.getValue();
            if (v.ciudadOrigen().equals(origen) &&
                v.ciudadDestino().equals(destino) &&
                //v.getFechaInicio().equals(fecha) &&
                (horaSalida == null || v.horaSalida().equals(horaSalida)))
            {
                v.setFechaInicio(fechaSalida);

                ItinerarioConcreto ic = new ItinerarioConcreto();

                // Añadimos todos lo trayectos (en realidad sólo 1) del viaje 1 al itinerario
                for (Trayecto t : v.getTrayectos())
                {
                    InformacionTrayecto it =
                            new InformacionTrayecto(t.getCiudadOrigen(),
                                                    t.getCiudadDestino(),
                                                    t.getHoraSalida(),
                                                    t.getHoraLlegada(),
                                                    t.calcularPrecio());
                    ic.add(it);
                }

                // Añadimos el itinerario a la solución
                resultado.add(ic);
            }
        }

        List<List<Viaje>> viajes_trasbordos = buscarTrasbordos(origen, destino, fechaSalida, horaSalida, horaLlegada);

        for (List<Viaje> viajes_trasbordo : viajes_trasbordos)
        {
            if (viajes_trasbordo.size() >= 2)
            {
                // Trasbordo
                ItinerarioConcreto ic = new ItinerarioConcreto();

                for (Viaje v : viajes_trasbordo)
                {
                    // Añadimos todos lo trayectos (en realidad sólo 1) del viaje 1 al itinerario
                    for (Trayecto t : v.getTrayectos())
                    {
                        InformacionTrayecto it =
                                new InformacionTrayecto(t.getCiudadOrigen(),
                                                        t.getCiudadDestino(),
                                                        t.getHoraSalida(),
                                                        t.getHoraLlegada(),
                                                        t.calcularPrecio());
                        ic.add(it);
                    }
                }

                // Añadimos el itinerario a la solución
                resultado.add(ic);
            }
        }
        
        return resultado;
    }

    /**
     * Itinerarios disponibles con los criterios especificados
     * @param origen
     * @param destino
     * @param fechaSalida
     * @return lista de itinerarios disponibles
     */
    public List<Itinerario> getItinerarios(String origen, String destino, LocalDate fechaSalida)
    {
        return getItinerariosEntre(origen, destino, fechaSalida, null, null);
    }


    /**
     * @return código para el siguiente viaje a crear
     */
    private int getSiguienteIdViaje() {
        siguienteCodigoViaje++;
        return siguienteCodigoViaje;
    }

    /**
     * Buscar dos viajes para hacer trasbordo entre dos ciudades
     * @param origen
     * @param destino
     * @param fechaSalida
     * @param horaSalida
     * @param horaLlegada
     * @return los dos viajes que forman el viaje total
     */
    private List<List<Viaje>> buscarTrasbordos(String origen, String destino, LocalDate fechaSalida, LocalTime horaSalida, LocalTime horaLlegada)
    {
        List<List<Viaje>> resultado = new ArrayList<List<Viaje>>();
        for (Map.Entry<Integer, Viaje> entry1 : viajes.entrySet())
        {
            Viaje v1 = entry1.getValue();
            v1.setFechaInicio(fechaSalida);

            // Salida que queremos
            if (v1.ciudadOrigen().equals(origen) &&
                (horaSalida == null || v1.horaSalida().equals(horaSalida)))
            {

                for (Map.Entry<Integer, Viaje> entry2 : viajes.entrySet())
                {
                    Viaje v2 = entry2.getValue();
                    v2.setFechaInicio(fechaSalida);

                    // Llegada que queremos + condiciones de trasbordo
                    if (v2.ciudadDestino().equals(destino) &&
                        (horaLlegada == null || v2.horaLlegada().equals(horaLlegada)) &&

                        v1.ciudadDestino().equals(v2.ciudadOrigen()) &&
                        v1.horaLlegada().plusMinutes(10).isBefore(v2.horaSalida()))
                    {
                        List<Viaje> trasbordo = new ArrayList<Viaje>();
                        // Tenemos los dos viajes
                        trasbordo.add(v1);
                        trasbordo.add(v2);
                        // Añade el trasbordo al resultado
                        resultado.add(trasbordo);
                    }
                }
            }
        }
        
        return resultado;
    }
}
